#include <memory>
#include <string>
#include <iostream>
#include <sys/epoll.h>

#include "Log.hpp"
#include "Socket.hpp"

using namespace socket_ns;

// 多路转接：事件循环、事件派发、事件处理！
class EpollServer
{
    const static int size = 128; // 无具体含义：epoll_create第一个参数要用到！
    const static int num = 128;

public:
    // 构造函数
    EpollServer(uint16_t port)
        : _port(port), _listensock(std::make_unique<TcpSocket>())
    {
        _listensock->BuildListenSocket(port); // 根据传来的port来创建监听套接字！！！

        // 1、epoll_create创建成功，说明底层内核已经创建好了：红黑树+就绪队列
        _epfd = ::epoll_create(size);
        if (_epfd < 0)
        {
            LOG(FATAL, "epoll create fail\n");
            exit(-1);
        }
        LOG(INFO, "epoll create sucess ,epfd:%d\n", _epfd);
    }

    void InitServer()
    {
        // 新链接到来，就认为是读事件就绪！
        struct epoll_event ev;
        ev.events = EPOLLIN;
        ev.data.fd = _listensock->Sockfd();

        // 必须将_listensock加入到epoll里面，这样才能让epoll对_listensock进行关心！
        int n = ::epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock->Sockfd(), &ev);

        if (n < 0)
        {
            std::cerr << "add fail to epoll" << std::endl;
            exit(-1);
        }
        LOG(INFO, "add sucess to epoll,new sockfd:%d\n", _listensock->Sockfd());
    }

    std::string EventsToString(uint32_t events)
    {
        std::string eventstr;
        if (events & EPOLLIN)
            eventstr = "EPOLLIN";
        if (events & EPOLLOUT)
            eventstr +="EPOLLOUT";

        return eventstr;
    }

    void Accepter()
    {
        InetAddr addr;
        int sockfd = _listensock->Accepter(&addr);

        if (sockfd < 0)
        {
            LOG(ERROR, "获取连接失败\n");
            return ;
        }

        LOG(INFO, "得到一个新链接：%d,客户端信息：%s:%d\n", sockfd, addr.Ip().c_str(), addr.Port());

        struct epoll_event ev;
        ev.events = EPOLLIN;
        ev.data.fd = sockfd;

        // 必须将sockfd加入到epoll里面，这样才能让epoll对提取出来的sockfd进行处理！
        ::epoll_ctl(_epfd, EPOLL_CTL_ADD, sockfd, &ev);

        LOG(INFO, "add sucess to epoll,new sockfd:%d\n", sockfd);
    }

    void HandlerIO(int fd)
    {
        char buffer[4096];
        int n = ::recv(fd, buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << buffer;
        }
        else if (n == 0)
        {
            LOG(INFO, "client,quit...\n");
            // 先将这个推出的fd从epoll中移除，再关闭fd
            ::epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);
            ::close(fd);
        }
        else
        {
            LOG(ERROR, "client,fail...\n");
            ::epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);
            ::close(fd);
        }
    }

    // 事件派发
    void HandlerEvent(int n)
    {
        for (int i = 0; i < n; i++)
        {
            int fd = revs[i].data.fd;
            uint32_t revents = revs[i].events; // 具体是哪一个fd里面的什么事件就绪了！？

            LOG(INFO, "%d 上面的有事件就绪了，具体事件是：%s\n", fd, EventsToString(revents).c_str());

            // 事件处理：封装！

            // 1、listensock就绪
            if (fd = _listensock->Sockfd())
            {
                Accepter();
            }
            // 2、处理普通fd
            else
            {
                HandlerIO(fd);
            }
        }
    }

    // 事件循环
    void Loop()
    {
        int timeout = 1000; // 设置时限！1s

        while (true)
        {
            int n = ::epoll_wait(_epfd, revs, num, timeout); // epoll_wait的返回值就是就绪事件的个数
            switch (n)
            {
            case 0:
                LOG(INFO, "epoll time out...\n");
                break;
            case -1:
                LOG(ERROR, "epoll wait fail\n");
                break;
            default:
                LOG(INFO, "have event happend! n:%d\n", n);
                HandlerEvent(n);
                break;
            }
        }
    }

    ~EpollServer()
    {
        if (_epfd >= 0)
            ::close(_epfd);
        _listensock->Close();
    }

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listensock;

    int _epfd;                    // epoll_create的返回值！epoll句柄
    struct epoll_event revs[num]; // 将内核epoll模型里面的就绪事件，存入revs(revs是epoll_wait的参数)
};